package jia;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;
import java.util.logging.Level;

import prop.PropertiesReader;

import env.WorldModel;

import arch.CowboyArch;
import arch.LocalWorldModel;

import jason.asSemantics.*;
import jason.asSyntax.*;
import jason.environment.grid.Location;

public class scout_target extends DefaultInternalAction {
	CowboyArch arch;
	LocalWorldModel model;
	TransitionSystem ts;
	public static final int SIGHT_RANGE = 8;

	@Override
	public Object execute(TransitionSystem trans, Unifier un, Term[] args)
			throws Exception {
		arch = (CowboyArch) trans.getUserAgArch();
		model = arch.getModel();
		ts = trans;
		try {
		
			double visited = 0;
			for(int i = 0; i < model.agentVisits.length; i++) {
				for(int j = 0; j < model.agentVisits[i].length; j++) {
					if(model.agentVisits[i][j] > 0) {
						visited++;
					}
				}
			}
			
			double mapSize = model.getWidth() * model.getHeight();
			
			if((visited / mapSize) > PropertiesReader.getInstance().getDoubleProperty("exploration_ratio")) {
				return false;
			}

			int agX = (int) ((NumberTerm) args[0]).solve();
			int agY = (int) ((NumberTerm) args[1]).solve();

			Point agent = new Point(agX, agY);
			Point target = decideExplorationPoint(agent);
			
			if (target != null) {
				// Tell leader target, so agents can take this target into consideration as well
				Message m = new Message("tell", null, "agent1", "target_pos(" + target.x + ", " + target.y + ")");
//				System.out.println("Sending target position: " + target);
				trans.getUserAgArch().sendMsg(m);
				
				return un.unifies(args[2], new NumberTermImpl(target.x))
						&& un.unifies(args[3], new NumberTermImpl(target.y));
			}

		} catch (Throwable e) {
			trans.getLogger().info("Scout_target error");
			e.printStackTrace();
		}
		return false;
	} // execute

	private Point decideExplorationPoint(final Point agent) {
		if(model.scoutStepsWithTarget < PropertiesReader.getInstance().getIntegerProperty("min_exploration_steps")) {
			model.scoutStepsWithTarget++;
			
			if(model.scoutCurrentTarget != agent) {
				return model.scoutCurrentTarget;
			}
		}

		model.scoutStepsWithTarget = 0;
				
		ArrayList<Point> addedPoints = model.scoutVisited;
	
		int[][] heatmap = model.agentVisits;
		Point target = getTarget(agent, heatmap, addedPoints);
		addedPoints.add(new Point(target));

		model.scoutCurrentTarget = target;
		return target;
	}

	private Point getTarget(final Point agent, int[][] heatmap, ArrayList<Point> oldTargets) {
		Point target = new Point(agent);
		double minDist = Double.MAX_VALUE;
		boolean mustBeNearObstacle = false;
		for (int x = 0; x < heatmap.length; x++) {
			outer: for (int y = 0; y < heatmap[0].length; y++) {
				// agents location
				if (x == agent.x && y == agent.y)  
					continue;
				
				if(model.hasObject(LocalWorldModel.OBSTACLE, x, y))
					continue;
				
				// is near target
				for(int i = x - 3; i <= x + 3; i++) {
					for(int j = y - 3; j <= y + 3; j++) {
						if(oldTargets.contains(new Point(i, j))) {
							continue outer;
						}
					}
				}

				Point tmp = new Point(x, y);
				double dist = tmp.distance(agent);
				if (heatmap[x][y] == 0 && dist < minDist) {
					if (mustBeNearObstacle && obstacleNear(tmp, SIGHT_RANGE)) {
						target = tmp;
						minDist = dist;
					} else if (!mustBeNearObstacle) {
						if (obstacleNear(tmp, SIGHT_RANGE)) 
							mustBeNearObstacle = true;
						target = tmp;
						minDist = dist;
					}
					
				}
			}
		}
		
		return target;
	}

	private boolean obstacleNear(Point undiscovered, int range) {
		for (int i = undiscovered.x - range; i < undiscovered.x + range; i++) {
			for (int j = undiscovered.y - range; j < undiscovered.y + range; j++) {
				if (model.hasObject(WorldModel.OBSTACLE, i, j))
					return true;
			}
		}

		return false;
	}

}
